﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Diagnostics;
using YetAnotherChat.TcpServer.WebSocketProtocols;
using System.Net;
using Newtonsoft.Json;

namespace YetAnotherChat.TcpServer
{
    /// <summary>
    /// Wraps around a TcpClient, this client must be a already initiated client, 
    /// which means a websocket browser already connected to a server, 
    /// or a TcpClient already connected to a server. After initiated, Connection will wrap around TcpClient
    /// to send + receive msg async, use protocol factory to create protocol to authenticate
    /// and send dataframes.
    /// 
    /// This is base class. ServerConnection wraps around TcpClient that server use to send/receive msg to websocket browser.
    /// ClientConnection wraps around TcpClient that the client use to send/receive msg to a server.
    /// </summary>
    public class Connection: IDisposable
    {
        #region Fields
        //private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// The buffer used for accepting raw data from the socket.
        /// </summary>
        internal protected byte[] Buffer;

        /// <summary>
        /// Whether or not the TCPClient is still connected.
        /// </summary>
        public bool Connected = true;

        /// <summary>
        /// The raw client connection.
        /// </summary>
        public TcpClient TcpClient;

        /// <summary>
        /// The current connection handler.
        /// </summary>
        protected WebSocketProtocolFactory _protocolFactory;

        /// <summary>
        /// The Header
        /// </summary>
        public Header Header;

        /// <summary>
        /// Whether or not this client has passed all the setup routines for the current handler(authentication, etc)
        /// </summary>
        public Boolean IsSetup { get; internal set; }

        /// <summary>
        /// The max frame that we will accept from the client
        /// </summary>
        public UInt64 MaxFrameSize = 102400; //100kb

        /// <summary>
        /// Semaphores that limit sends and receives to 1 and a time.
        /// </summary>
        protected SemaphoreSlim ReceiveReady = new SemaphoreSlim(1);

        /// <summary>
        /// How many bytes we received this tick.
        /// </summary>
        internal protected int ReceivedByteCount;

        protected SemaphoreSlim SendReady = new SemaphoreSlim(1);

        protected IWebSocketProtocol _protocol;
        public const string NotImplemented = "HTTP/1.1 501 Not Implemented";
        public const string NotAuthenticated = "UnAuthorized";

        protected int _bufferSize = 512;


        protected SocketAsyncEventArgs ReceiveEventArgs { get; set; }

        protected SocketAsyncEventArgs SendEventArgs { get; set; }


        public string RequestPath { get; internal set; }

        /// <summary>
        /// Configuration for the above heartbeat setup.
        /// TimeOut : How long until a connection drops when it doesn't receive anything.
        /// MaxPingsInSequence : A multiple of TimeOut for how long a connection can remain idle(only pings received) before we kill it.
        /// </summary>
        protected TimeSpan TimeOut = TimeSpan.FromMinutes(1);

        /// <summary>
        /// This Id will be used to linked to user or something else
        /// </summary>
        public Guid ConnectionId { get; set; }

        public EndPoint ClientAddress
        {
            get
            {
                return TcpClient.Client.RemoteEndPoint;
            }
        }

        public IWebSocketProtocol Protocol
        {
            get
            {
                return _protocol;
            }
            protected internal set
            {
                _protocol = value;
            }
        }

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="Context"/> class.
        /// </summary>
        public Connection(TcpClient connection, WebSocketProtocolFactory webSocketProtocolFactory)
        {
            TcpClient = connection;
            Buffer = new byte[_bufferSize];

            ReceiveEventArgs = new SocketAsyncEventArgs();
            SendEventArgs = new SocketAsyncEventArgs();

            _protocolFactory = webSocketProtocolFactory;
           
        }

        /// <summary>
        /// Setups some event handlers for send and receive completed
        /// </summary>
        public virtual void SetupSendReceiveProcess()
        {
            ConnectionId = Guid.NewGuid();
            ReceiveEventArgs.UserToken = this;
            ReceiveEventArgs.Completed += ReceiveEventArgs_Completed;
            ReceiveEventArgs.SetBuffer(Buffer, 0, Buffer.Length);
            SendEventArgs.Completed += SendEventArgs_Completed;
            Debug.WriteLine(string.Format("Done setup connection events, starting receive"));
            StartReceive();
        }

       
        #region sending data
        void SendEventArgs_Completed(object sender, SocketAsyncEventArgs e)
        {
            Debug.WriteLine(string.Format("Sent some message, release semaphore and raise user context onsend event "));

            HandlerMessage message = (HandlerMessage)e.UserToken;

            if (e.SocketError != SocketError.Success)
            {
                message.Connection.Disconnect();
                return;
            }

            message.Connection.SendReady.Release();
            //message.Connection.Server.OnMessgeSent(new MessageEventArgs(message.Connection));
            OnMessageSent(message.Connection);

            if (message.DoClose)
            {
                message.Connection.Disconnect();
            }
        }

        protected virtual void OnMessageSent(Connection con)
        {
            
        }

        /// <summary>
        /// Sends the specified data.
        /// </summary>
        /// <param name="dataFrame">The data.</param>
        /// <param name="raw">Whether or not to send raw data</param>
        /// <param name="close">if set to <c>true</c> [close].</param>
        public void Send(DataFrameBase dataFrame, bool raw = false, bool close = false)
        {
            if (Connected)
            {
                HandlerMessage message = new HandlerMessage { DataFrame = dataFrame, Connection = this, IsRaw = raw, DoClose = close };
                SendEventArgs.UserToken = message;

                try
                {
                    SendReady.Wait(/*cancellation.Token*/);
                }
                catch (OperationCanceledException)
                {
                    return;
                }

                try
                {
                    List<ArraySegment<byte>> data = raw ? dataFrame.AsRaw() : dataFrame.AsFrame();
                    SendEventArgs.BufferList = data;
                    TcpClient.Client.SendAsync(SendEventArgs);
                    Debug.WriteLine(string.Format("Send async a msg."));
                }
                catch
                {
                    Disconnect();
                }
            }
        }

        /// <summary>
        /// Sends the specified data.
        /// </summary>
        /// <param name="aString">The data.</param>
        /// <param name="raw">whether or not to send raw data</param>
        /// <param name="close">if set to <c>true</c> [close].</param>
        public void Send(String aString, bool raw = false, bool close = false)
        {
            DataFrameBase dataFrame = _protocol.DataFrame.CreateInstance();
            dataFrame.Append(aString);
            Send(dataFrame, raw, close);
        }

        /// <summary>
        /// Sends the specified data.
        /// </summary>
        /// <param name="someBytes">The data.</param>
        /// <param name="raw">whether or not to send raw data</param>
        /// <param name="close">if set to <c>true</c> [close].</param>
        public void Send(byte[] someBytes, bool raw = false, bool close = false)
        {
            DataFrameBase dataFrame = _protocol.DataFrame.CreateInstance();
            dataFrame.IsByte = true;
            dataFrame.Append(someBytes);
            Send(dataFrame, raw, close);
        }

        public void Send(object obj, bool raw = false, bool close = false)
        {
            var s = JsonConvert.SerializeObject(obj);
            Send(s, raw, close);
        }
        #endregion

       
        #region receiving
        private void StartReceive()
        {
            try
            {
                if (ReceiveReady.Wait(TimeOut/*, cancellation.Token*/))
                {
                    try
                    {
                        if (!TcpClient.Client.ReceiveAsync(ReceiveEventArgs))
                        {
                            ReceiveEventArgs_Completed(TcpClient.Client, ReceiveEventArgs);
                        }
                    }
                    catch (SocketException ex)
                    {
                        Debug.WriteLine(string.Format("Disconnecting because receiving error {0}", ex));
                        Disconnect();
                    }
                }
                else
                {
                    Debug.WriteLine(string.Format("Disconnecting because of timeout or cancelled"));
                    Disconnect();
                }
            }
            catch (OperationCanceledException) { }
        }

        void ReceiveEventArgs_Completed(object sender, SocketAsyncEventArgs e)
        {
            Debug.WriteLine(string.Format("Received some data"));
            var connection = (Connection)e.UserToken;
            connection.Reset();
            if (e.SocketError != SocketError.Success)
            {
                //logger.Error("Socket Error: " + e.SocketError.ToString());
                connection.ReceivedByteCount = 0;
            }
            else
            {
                connection.ReceivedByteCount = e.BytesTransferred;
            }

            

            if (connection.ReceivedByteCount > 0)
            {
                Debug.WriteLine(string.Format("Start processing, connection is setup {0}, then start receiving async again.", IsSetup));
                ProcessReceivedMessage();
                //connection.Handler.HandleRequest(connection);
                connection.ReceiveReady.Release();
                StartReceive();
            }
            else
            {
                Debug.WriteLine(string.Format("Received byte count 0, disconnecting"));
                connection.Disconnect();
                connection.ReceiveReady.Release();
            }
        }

        #endregion

        

        protected virtual void ProcessReceivedMessage()
        {
            
        }

        /// <summary>
        /// Gets or sets the size of the buffer.
        /// </summary>
        /// <value>
        /// The size of the buffer.
        /// </value>
        public int BufferSize
        {
            get { return _bufferSize; }
            set
            {
                _bufferSize = value;
                Buffer = new byte[_bufferSize];
            }
        }

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            //logger.Debug("Disconnected in " + Environment.StackTrace);
            Debug.WriteLine(string.Format("Disposing a connection, address {0}", ClientAddress));
            Connected = false;
            ReceiveEventArgs.Completed -= ReceiveEventArgs_Completed;
            SendEventArgs.Completed -= SendEventArgs_Completed;

            // close client connection
            if (TcpClient != null)
            {
                try
                {
                    TcpClient.Close();
                }
                catch (Exception)
                {
                    // skip
                }
            }
            SendReady.Release();
            ReceiveReady.Release();
        }

        #endregion

        /// <summary>
        /// Disconnects the client
        /// </summary>
        public void Disconnect()
        {
            Connected = false;
            OnClientDisconnecting();
            
            ThreadPool.QueueUserWorkItem(CleanUpConnection, this);
        }

        protected virtual void OnClientDisconnecting()
        { 
        
        }

        void CleanUpConnection(object data)
        {
            var con = (Connection)data;
            Debug.WriteLine(string.Format("Started clean up connection."));
            SendReady.Wait();
            ReceiveReady.Wait();
            //con.Server.RemoveConnection(con);
            CleanUpConnection(con);
            con.Dispose();
        }

        protected virtual void CleanUpConnection(Connection con)
        { 
        }

        /// <summary>
        /// Resets this instance.
        /// Clears the dataframe if necessary. Resets Received byte count.
        /// </summary>
        public void Reset()
        {
            if (_protocol !=null && _protocol.DataFrame != null)
            {
                if (_protocol.DataFrame.State == DataFrameBase.DataState.Complete)
                {
                    _protocol.DataFrame.Reset();
                }
            }
            ReceivedByteCount = 0;
        }

        private class HandlerMessage
        {
            public DataFrameBase DataFrame { get; set; }
            public Connection Connection { get; set; }
            public Boolean IsRaw { get; set; }
            public Boolean DoClose { get; set; }
        }


        
    }
}
